// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-08-02

package base

import (
	"fmt"
	"regexp"
	"strings"
)

var (
	RegexStartLetter         = regexp.MustCompile(`^(\S)`)
	RegexStartWithUnderline  = regexp.MustCompile(`^_+`)
	RegexMiddleWithUnderline = regexp.MustCompile(`_+(\S)`)
	RegexMatchUpperLetter    = regexp.MustCompile(`([A-Z])`)
	RegexMatchManyUnderlines = regexp.MustCompile(`_+`)

	RegexMatchColumnExtraAutoIncrement = regexp.MustCompile(`auto_increment`)
	RegexMatchColumnType               = regexp.MustCompile(`^([_a-zA-Z0-9]+)`)
)

// ColumnDefault
// is a type for field name of a table.
type ColumnDefault string

func (o ColumnDefault) IsCurrentTimestamp() bool {
	return strings.ToUpper(o.String()) == "CURRENT_TIMESTAMP"
}

func (o ColumnDefault) IsZeroTimestamp() bool {
	return regexp.MustCompile(`^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$`).MatchString(o.String())
}

func (o ColumnDefault) String() string {
	return strings.TrimSpace(strings.ToLower(string(o)))
}

// +---------------------------------------------------------------------------+
// | Type for table column extra                                               |
// +---------------------------------------------------------------------------+

// ColumnExtra
// is a type for field name of a table.
type ColumnExtra string

// Convert
// field name to exported orm name.
//
//	.Convert("id")          // return "id pk autoincr"
//	.Convert("user_id")     // return "user_id"
func (o ColumnExtra) Convert(field ItemName) string {
	if RegexMatchColumnExtraAutoIncrement.MatchString(o.String()) {
		return fmt.Sprintf(`%s pk autoincr`, field.String())
	}
	return field.String()
}

func (o ColumnExtra) IsDatetimeOnUpdate() bool {
	return o.String() == "on update current_timestamp"
}

func (o ColumnExtra) String() string {
	return strings.TrimSpace(strings.ToLower(string(o)))
}

// +---------------------------------------------------------------------------+
// | Type for key of a table                                                   |
// +---------------------------------------------------------------------------+

// ColumnKey
// is a type for field name of a table.
type ColumnKey string

func (o ColumnKey) IsPrimary() bool {
	return strings.ToUpper(o.String()) == "PRI"
}

func (o ColumnKey) String() string {
	return string(o)
}

// +---------------------------------------------------------------------------+
// | Type for table column nullable                                            |
// +---------------------------------------------------------------------------+

// ColumnNull
// is a type for field name of a table.
type ColumnNull string

// +---------------------------------------------------------------------------+
// | Type for table column                                                     |
// +---------------------------------------------------------------------------+

// ColumnType
// is a type for field name of a table.
type ColumnType string

func (o ColumnType) IsDate() bool {
	if m := RegexMatchColumnType.FindStringSubmatch(o.String()); len(m) > 0 {
		return strings.ToLower(m[1]) == "date"
	}
	return false
}

func (o ColumnType) IsDatetime() bool {
	if m := RegexMatchColumnType.FindStringSubmatch(o.String()); len(m) > 0 {
		s := strings.ToLower(m[1])
		return s == "datetime" || s == "timestamp"
	}
	return false
}

func (o ColumnType) Origin() string {
	if m := RegexMatchColumnType.FindStringSubmatch(o.String()); len(m) > 0 {
		return m[1]
	}
	return ""
}

func (o ColumnType) String() string {
	return string(o)
}

// +---------------------------------------------------------------------------+
// | Collation for table and columns                                           |
// +---------------------------------------------------------------------------+

// ItemCollation
// is a type for table and field name.
type ItemCollation string

// +---------------------------------------------------------------------------+
// | Comment for table and columns                                             |
// +---------------------------------------------------------------------------+

// ItemComment
// is a type for table and field name.
type ItemComment string

// String
// returns a comment string without new line.
func (o ItemComment) String() string {
	return strings.TrimSpace(string(o))
}

// Strings
// returns a comment string list with new line.
func (o ItemComment) Strings() []string {
	return strings.Split(o.String(), "\n")
}

// +---------------------------------------------------------------------------+
// | Name for table and columns                                                |
// +---------------------------------------------------------------------------+

// ItemName
// is a type for table and field name.
type ItemName string

// Allow
// return true if start with an a-z letter.
//
// Output:
//
//	return true  // user
//	return false // _user
func (o ItemName) Allow() bool {
	return regexp.MustCompile(`^[a-zA-Z][_a-zA-Z0-9]*$`).MatchString(o.String())
}

// String
// returns the origin name with any formats.
//
// Output:
//
//	return "user_info"
//	return "UserInfo"
//	return "userInfo"
func (o ItemName) String() string {
	return string(o)
}

// FileName
// convert to file name without extension name.
//
// Rule:
//   - table name : `cdb_user_info`
//   - prefix     : `cdb_`
//   - file name  : `user_info`
//
// Output:
//
//	return "user_info"
func (o ItemName) FileName(prefix string) (name string) {
	// Use
	// origin name as default.
	name = o.String()

	// Remove
	// prefix name.
	//
	// Convert `cdb_user_info` as `user_info` with a prefix `cdb_`.
	if prefix != "" {
		name = strings.TrimPrefix(name, prefix)
	}

	// Convert
	// upper-case letter as underline with a lower-case letter.
	//
	// Convert `UserInfo` as `_user_info`
	name = RegexMatchUpperLetter.ReplaceAllStringFunc(name, func(s string) string {
		return fmt.Sprintf(`_%s`, strings.ToLower(s))
	})

	// Remove
	// starts with underline.
	//
	// Convert `_user_info` as `user_info`.
	name = RegexStartWithUnderline.ReplaceAllString(name, "")
	name = RegexMatchManyUnderlines.ReplaceAllString(name, "_")
	return
}

// StructName
// convert to struct name that starts with an upper-case letter.
//
// Rule:
//   - table name  : `cdb_user_info`
//   - prefix      : `cdb_`
//   - struct name : `UserInfo`
//
// Output:
//
//	return "UserInfo"
func (o ItemName) StructName(prefix string) (name string) {
	// Use
	// origin name as default.
	name = o.String()

	// Remove
	// prefix name.
	//
	//   Example  : cdb_user
	//   Prefix   : cdb_
	//   Replaced : user
	if prefix != "" {
		name = strings.TrimPrefix(name, prefix)
	}

	// Remove
	// underline start of name.
	name = RegexStartWithUnderline.ReplaceAllString(name, "")

	// Convert
	// underline with any letter as upper-case letter.
	name = RegexMiddleWithUnderline.ReplaceAllStringFunc(name, func(s string) string {
		if m := RegexMiddleWithUnderline.FindStringSubmatch(s); len(m) > 0 {
			return strings.ToUpper(m[1])
		}
		return s
	})

	// Convert
	// first letter as upper-case letter.
	name = RegexStartLetter.ReplaceAllStringFunc(name, func(s string) string {
		return strings.ToUpper(s)
	})
	return
}

// ToLargeCamel
// returns a large camel that equals to struct name.
//
// Output:
//
//	return "UserInfo"
func (o ItemName) ToLargeCamel() string {
	return o.StructName("")
}

// ToSmallCamel
// returns a small-camel format name.
//
// Output:
//
//	return "userInfo"
func (o ItemName) ToSmallCamel() string {
	return RegexStartLetter.ReplaceAllStringFunc(o.StructName(""), func(s string) string {
		return strings.ToLower(s)
	})
}

// ToSnake
// returns a snake format name.
//
// Output:
//
//	return "user_info"
func (o ItemName) ToSnake() string {
	return o.FileName("")
}
